package com.yifeng.repo.micro.service.server.engine.dal.tx;

import com.yifeng.repo.micro.service.server.engine.configure.ForceTransactionProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.SpringTransactionAnnotationParser;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.util.PatternMatchUtils;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

/**
 * Created by daibing on 2023/6/9.
 */
@Slf4j
public class ForceSpringTransactionAnnotationParser extends SpringTransactionAnnotationParser {
    private final ForceTransactionProperties properties;
    private final String[] excludeClassNamePatterns;
    private final Set<String> writeMethodPrefixList;

    public ForceSpringTransactionAnnotationParser(ForceTransactionProperties properties) {
        this.properties = properties;
        this.excludeClassNamePatterns = Optional.ofNullable(properties.getExcludeClassNamePrefixList()).orElse(Collections.emptyList()).toArray(new String[0]);
        this.writeMethodPrefixList = new HashSet<>(properties.getWriteMethodPrefixList());
        this.writeMethodPrefixList.addAll(Optional.ofNullable(properties.getExtraWriteMethodPrefixList()).orElse(Collections.emptyList()));
    }

    @Override
    public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
        AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
                element, Transactional.class, false, false);
        // 带 @Transactional 的方法优先级高于强事务管理
        if (attributes != null) {
            return parseTransactionAnnotation(attributes);
        }
        // 元素是类直接退出：仅拦截方法
        if (element instanceof Class) {
            return null;
        }
        // 不带 @Service 的类直接退出：仅拦截方法归属类带@Service的方法
        Class<?> clazz = ((Method) element).getDeclaringClass();
        if (!clazz.isAnnotationPresent(Service.class)) {
            return null;
        }

        // 支持前缀匹配排除：类似配置 com.yifeng.base.sms.server.boss.manager.UserManager.getUser
        String methodName = ((Method) element).getName();
        String target = clazz.getName() + "." + methodName;
        if (PatternMatchUtils.simpleMatch(excludeClassNamePatterns, target)) {
            return null;
        }
        // 支持前缀匹配写事务，以及打印强事务管理的类方法
        boolean write = writeMethodPrefixList.stream().anyMatch(methodName::startsWith);
        if (properties.isPrintLog()) {
            log.info("Enable force {} transaction: target={}", write ? "write" : "read", target);
        }
        return parseCustomTransaction(!write);
    }

    private TransactionAttribute parseCustomTransaction(boolean readOnly) {
        RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
        rbta.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        rbta.setIsolationLevel(Isolation.DEFAULT.value());
        rbta.setTimeout(TransactionDefinition.TIMEOUT_DEFAULT);
        if (readOnly) {
            rbta.setReadOnly(true);
        } else {
            rbta.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
        }
        return rbta;
    }
}
